//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<CTexture2D9.cpp>
///	@path	~/src/graphics/dx9/
///	@date	2007/12/13
///	@desc	Base implementation of texture in DX 9.

#include "config/config.h"

#include "lib/system/thread_debug.h"
#include "lib/math/math_win32.h"

#include "graphics/DxHelpers.h"
#include "graphics/dx9/CSprite9.h"
#include "graphics/dx9/CTexture2D9.h"

namespace xeres {

	IMPL_IOBJECT_CLASS( CTexture2D9 );

	// CTexture2D9
	CTexture2D9::CTexture2D9( IDirect3DTexture9 * pTexture9 )
		: m_pTexture9( pTexture9 )
	{
		if( m_pTexture9 )
		{
			HRESULT hr = m_pTexture9->GetLevelDesc( 0 , &m_desc );
			XS_ASSERT( SUCCEEDED(hr) );
		}
		else
		{
			memset( &m_desc , 0 , sizeof(D3DSURFACE_DESC) );
		}
	}

	// ~CTexture2D9
	CTexture2D9::~CTexture2D9( void )
	{
		if( m_pTexture9 )
		{
			UINT rest = m_pTexture9->Release();
			if( rest != 0 )
			{
				TRACE_ERROR( _S("CTexture2D9::DTOR: Invalid IDirect3DTexture9 reference count.") );
				FATAL_ABORT( _S("Invalid IDirect3DTexture9 reference count.") );
			}
		}
	}

	// OnLostDevice
	bool CTexture2D9::OnLostDevice( void )
	{
		// we don't need to re-create the texture.
		return true;
	}

	// OnResetDevice
	bool CTexture2D9::OnResetDevice( RefWeak<IDevice> device )
	{
		// we don't need to re-create the texture.
		return true;
	}

	// GetType
	TextureType CTexture2D9::GetType( void )
	{
		return TEXTURE_2D;
	}

	// GetFormat
	DXFormat CTexture2D9::GetFormat( void )
	{
		if( m_pTexture9 == NULL )
			DXFormat();
		return DXFormat( m_desc.Format );
	}

	// GetSize
	Pair<int> CTexture2D9::GetSize( void )
	{
		if( m_pTexture9 == NULL )
			return Pair<int>( 0 , 0 );
		return Pair<int>( m_desc.Width , m_desc.Height );
	}

	// GetDepth
	size_t CTexture2D9::GetDepth( void )
	{
		FATAL_ABORT( _S("Try to get depth from 2D texture.") );
		return 0;
	}

	// IsAccessable
	bool CTexture2D9::IsWritable( void )
	{
		return (m_desc.Pool != D3DPOOL_DEFAULT);
	}
	// IsWriteOnly
	bool CTexture2D9::IsReadable( void )
	{
		return (m_desc.Pool != D3DPOOL_DEFAULT);
	}

	// Map
	bool CTexture2D9::Map( uint sub , ResourceMap& data )
	{
		if( m_pTexture9 == NULL )
			return false;

		if( sub >= m_pTexture9->GetLevelCount() )
		{
			TRACE_ERROR( _S("CTexture2D9::Map: Level exceeded limit.") );
			return false;
		}

		if( m_desc.Pool == D3DPOOL_DEFAULT )
		{
			TRACE_ERROR( _S("CTexture2D9::Map: Default pool object is not lockable.") );
			return false;
		}

		D3DLOCKED_RECT locked;
		memset( &locked , 0 , sizeof(D3DLOCKED_RECT) );
		HRESULT hr = m_pTexture9->LockRect( sub , &locked , NULL , 0 );
		if( FAILED(hr) )
		{
			TRACE_ERROR( _S("CTexture2D9::Map: Failed to lock texture: %s") ,
				d3d_get_err(hr) );
			return false;
		}
		size_t total = d3d_get_color_size( m_desc.Format ) * m_desc.Height * locked.Pitch;
		data.Map( (byte*)locked.pBits , total , locked.Pitch , m_desc.Height , m_desc.Width );
		return true;
	}

	// ReadBack
	bool CTexture2D9::ReadBack( uint sub , ResourceMap& data , bool readonly )
	{
		if( m_pTexture9 == NULL )
			return false;

		if( sub >= m_pTexture9->GetLevelCount() )
		{
			TRACE_ERROR( _S("CTexture2D9::ReadBack: Level exceeded limit.") );
			return false;
		}

		if( m_desc.Pool == D3DPOOL_DEFAULT )
		{
			TRACE_ERROR( _S("CTexture2D9::ReadBack: Default pool object is not lockable.") );
			return false;
		}

		D3DLOCKED_RECT locked;
		memset( &locked , 0 , sizeof(D3DLOCKED_RECT) );
		HRESULT hr = m_pTexture9->LockRect( sub , &locked , NULL , readonly ? D3DLOCK_READONLY : 0 );
		if( FAILED(hr) )
		{
			TRACE_ERROR( _S("CTexture2D9::ReadBack: Failed to lock texture: %s") ,
				d3d_get_err(hr) );
			return false;
		}
		size_t total = d3d_get_color_size( m_desc.Format ) * m_desc.Height * locked.Pitch;
		data.Map( (byte*)locked.pBits , total , locked.Pitch , m_desc.Height , m_desc.Width );
		return true;
	}

	// Unmap
	bool CTexture2D9::Unmap( uint sub )
	{
		if( m_pTexture9 == NULL )
			return false;
		
		HRESULT hr = m_pTexture9->UnlockRect( sub );
		if( FAILED(hr) )
		{
			TRACE_ERROR( _S("CTexture2D9::Unmap: Failed to unlock texture: %s") ,
				d3d_get_err(hr) );
			return false;
		}
		return true;
	}

	// Update
	bool CTexture2D9::Update( uint sub , const Box<uint>& size , const byte * data , uint row_pitch , uint depth_pitch )
	{
		if( size.depth() > 0 || depth_pitch > 0 )
		{
			TRACE_ERROR( _S("CTexture2D9::Update: 2D texture does not support 3D content update.") );
			return false;
		}
		if( size.right() > m_desc.Width || size.bottom() > m_desc.Height )
		{
			TRACE_ERROR( _S("CTexture2D9::Update: Update region is larger texture size.") );
			return false;
		}

		if( sub >= m_pTexture9->GetLevelCount() )
		{
			TRACE_ERROR( _S("CTexture2D9::Update: Level exceeded limit.") );
			return false;
		}

		RECT rect;
		::SetRect( &rect , size.left() , size.top() , size.right() , size.bottom() );
		D3DLOCKED_RECT locked;
		memset( &locked , 0 , sizeof(D3DLOCKED_RECT) );

		const uint pixel_size = locked.Pitch / m_desc.Width;
		const uint row_size = pixel_size * size.width();

		const byte * src = data;
		byte * dst = (byte*)locked.pBits;
		HRESULT hr = m_pTexture9->LockRect( sub , &locked , &rect , 0 );
		for( uint i = 0 , total = size.height() ; i < total ; ++i )
		{
			memcpy( dst , src , row_size );
			dst += locked.Pitch;
			src += row_pitch;
		}

		m_pTexture9->UnlockRect( sub );

		return true;
	}

	// GetHandle
	IUnknown * CTexture2D9::GetTextureInterface( void )
	{
		return m_pTexture9;
	}

	IMPL_IOBJECT_CLASS( CTexture2DSurface9 );

	// CTextureSurface9
	CTexture2DSurface9::CTexture2DSurface9( IDirect3DSurface9 * surface )
		: CTexture2D9( GetTextureFromSurface( surface ) )
	{
	}
	// ~CTextureSurface9
	CTexture2DSurface9::~CTexture2DSurface9( void )
	{
		if( m_pTexture9 )
			m_pTexture9->Release();
		m_pTexture9 = NULL;
	}
	// GetTextureFromSurface
	IDirect3DTexture9 * CTexture2DSurface9::GetTextureFromSurface( IDirect3DSurface9 * surface )
	{
		void * pContainer = NULL;
		IDirect3DTexture9 *pTexture = NULL;
		HRESULT hr = surface->GetContainer( IID_IDirect3DTexture9, &pContainer );
		if( SUCCEEDED(hr) && pContainer )
		{
			pTexture = (IDirect3DTexture9 *)pContainer;
		}
		else
		{
			TRACE_ERROR( _S("CTexture2DSurface9::GetTextureFromSurface: Failed to get texture from surface: %s") ,
				d3d_get_err(hr) );
		}
		return pTexture;
	}

} // namespace xeres
